﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using DBSchemaViewer.BackEnd.Entities;
using DBSchemaViewer.Business.Controller.Messages;
using DBSchemaViewer.Business.Controller.IViews;
using DBSchemaViewer.FrontEnd.Main.Helpers;
using DBSchemaViewer.FrontEnd.DiagramUserControls;
using DBSchemaViewer.FrontEnd.DiagramUserControls.Interfaces;
using System.IO;
using ImageTools;
using DBSchemaViewer.FrontEnd.Main.AppUserControls;


namespace DBSchemaViewer.FrontEnd.Main
{
    [ScriptableType]
    public partial class Page : UserControl, IViewMainPage
    {
        public DynamicControlsListHelper _dynamicCOntrolsListHelper = new DynamicControlsListHelper();
        public RelationshipDraggingHelper _relationshipDraggingHelper = new RelationshipDraggingHelper();
        public MainPageHelper _mainPageHelper = new MainPageHelper();
        public const int __MarginXRelationDragVsMouse = 5;

        protected double _TableMoveByDraggingMouseBeginX = 0;
        protected double _TableMoveByDraggingMouseBeginY = 0;
        protected bool _TableMoveByDraggingTrackingMouse = false;
        protected CtlDBTable _TableMoveByDraggingTableObject = null;

        /// <summary>
        /// For this viewer this property will always be false (is just a viewer no creation at all)
        /// </summary>
        protected bool _CreateRelationByDraggingTrackingMouse = false;

        /// <summary>
        /// 
        /// Temp Relation Drag & Drop
        /// *************************
        /// 
        /// --> We are going to have a temporary relation object (normally hidden), member bar maybe null.
        /// --> Once a relation drag is started, set the fix origin (field), and point the destination on the mouse position (maybe vertical ? some offset to center)
        /// --> In the mouse move just update the destination of the relation.
        /// --> Once the user leaves the LButton Up, just remove the temporary relation.
        /// --> Enhancement, change mouse cursor !!! 
        /// --> lot of enhacnementes could be done (coloring destination field etc...) but right now not
        /// 
        /// </summary>
        protected CtlDBRelationship _temporaryRelationControlToShowMotionWhenDragging = null;
        protected EntityRelation _relationByDragging = null;

        ISelectable _currSelectedControl = null;


        public Page()
        {
            InitializeComponent();

            // http://www.silverlightshow.net/items/Tutorial-Create-a-Silverlight-2-User-Control-from-a-Popup-Control.aspx
            // Important thing... Use this to set up modal dialogs
            // when an user resizes the screen, we must resize as well
            // the modal gray background to the new size !!
            App.Current.Host.Content.Resized += (s, e) =>
            {
                // Resize the dar rectangle that makes the dialog modal
                this.Width = App.Current.Host.Content.ActualWidth;
                this.Height = App.Current.Host.Content.ActualHeight;

                // Diagram Area take the total available
                // Total Height of the container minus the toolbar height minus the scroll bar offset
                double newHeight = App.Current.Host.Content.ActualHeight /* 15*/;
                double newWidth = App.Current.Host.Content.ActualWidth /*- 5*/;

                //SystemParameters.VerticalScrollBarWidth

                // Right now we are just subtracting a harcoded value, that is the Horizontal Scroll Bar 
                // widht and height, a better approach could be to define the scrollbars widht and height
                // using an style, about how to do this:
                // http://msdn.microsoft.com/en-us/library/cc278065(VS.95).aspx
                ctlDiagramView.ScrViewer.Width = newWidth - 15;
                ctlDiagramView.ScrViewer.Height = newHeight - 35;
            };
            
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            ctlDiagramView.MouseMove += new MouseEventHandler(DiagramView_OnMouseMove);
            ctlDiagramView.MouseLeftButtonUp += new MouseButtonEventHandler(DiagramView_OnMouseLBUp);


            HtmlPage.RegisterScriptableObject("plDBSchemaViewer", this);

            
            // Not in use any more, the ASp .net wrapper and the object already got
            // a param to hook to a similar event
            // Indicate to the browser / HTML page that the application is up and running
            // waiting for their orders
            //System.Windows.Browser.HtmlPage.Window.CreateInstance("SL_DbSchemaViewer_Loaded");
        }


        /// <summary>
        /// This method can be called via javascript (ScriptableMember).
        /// 
        /// Here we will pass the XML content.
        /// </summary>
        /// <param name="xmlContent"></param>
        [ScriptableMember]
        public void LoadXMLContentIntoDBSchemaViewer(string strXmlContent)
        {

            ((App)(Application.Current)).Controller.RunCommand(
                                                               this,
                                                               new MsgLoadStringContentIntoDiag(MessageType.DOCUMENTOP_LoadStringXMLContentIntoDiagram) { xmlContent = strXmlContent }
                                                              );            
             
        }

        /// <summary>
        /// This mehtod is pure UI, wont' pass through the controller.
        /// 
        /// Just change the scale to perform zoom in / zoom out.
        /// 
        /// More code is needed to perform this operation: on the MouseMove
        /// we will need to multiply the current scale by the offset we want
        /// to move.
        /// </summary>
        /// <param name="p"></param>
        [ScriptableMember]
        public void ExecuteZoomOperation(bool zoomIn)
        {
            double zoomStep = (zoomIn) ? 0.2 : -0.2;
            bool zoomLimitReached = (!zoomIn && ctlDiagramView.stCurrentZoom.ScaleX < 0.4)
                                ||
                                (zoomIn && ctlDiagramView.stCurrentZoom.ScaleX > 2);


            if (!zoomLimitReached)
            {
                ctlDiagramView.stCurrentZoom.ScaleX += zoomStep;
                ctlDiagramView.stCurrentZoom.ScaleY += zoomStep;


                // TODO: Find a way to update the scroll View !!!!                
                ctlDiagramView.CheckForScrollBarsVisibility();
            }
        }


        /// <summary>
        /// Right now this only will be used to mark a relation as selected or unselected
        /// </summary>
        /// <param name="o"></param>
        /// <param name="e"></param>
        void RelationshipControl_LButtonDownBubbled(object o, DiagramUserControls.CustomEvents.RelationCtlMouseOperationBubblingArgs e)
        {
            CtlDBRelationship currRelationControl = o as CtlDBRelationship;

            if (currRelationControl != null)
            {
                SelectNewControl(currRelationControl);
            }
        }

        /// <summary>
        /// Single entry point for control selecting operation.
        /// TODO:
        ///   1. Beware, if we remove an object, we should wipe the selected control.
        ///   2. If we load a doc, do not forget to wipe the selected control after loading.
        /// </summary>
        /// <param name="controlToSelect"></param>
        private void SelectNewControl(ISelectable controlToSelect)
        {
            if (_currSelectedControl != null)
            {
                _currSelectedControl.unSelect();
            }

            _currSelectedControl = controlToSelect;
            _currSelectedControl.Select();            
        }

        /// <summary>
        /// In the table controls we allow two operations:
        ///     - Move the table control: drag and drop on the thable header.
        ///     - Create Database relations by dragging and dropping from on table field to another.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="e"></param>
        void TableControl_LButtonDownBubbled(object o, DiagramUserControls.CustomEvents.TableCtlMouseOperationBubblingArgs e)
        {
            _TableMoveByDraggingMouseBeginX = e.mousePosition.X;
            _TableMoveByDraggingMouseBeginY = e.mousePosition.Y;

            CtlDBTable selectedTableControl = o as CtlDBTable;
            SelectNewControl(selectedTableControl);

            switch (e.mouseCommand)
            {
                case DiagramUserControls.CustomEvents.TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum.DRAG_TABLE:
                    _TableMoveByDraggingTableObject = selectedTableControl;
                    _TableMoveByDraggingTableObject.CaptureMouse();
                    _TableMoveByDraggingTrackingMouse = true;
                    this.Cursor = Cursors.Hand;
                    break;


            }
        }

        void DiagramView_OnMouseMove(object sender, MouseEventArgs e)
        {
            double currentX = 0;
            double currentY = 0;

            currentX = e.GetPosition(null).X;
            currentY = e.GetPosition(null).Y;

            // ***************
            // Table Dragging
            // ***************
            if (_TableMoveByDraggingTrackingMouse)
            {
                // Quite important here to divide by the current scale (zoom in, zoom out)
                // just on the offset when moving the control, the rest is performed automatically
                // by SL engine
                // ctlDiagramView.stCurrentZoom.ScaleX
                // ctlDiagramView.stCurrentZoom.ScaleY

                double positionDeltaX = (currentX - _TableMoveByDraggingMouseBeginX) / ctlDiagramView.stCurrentZoom.ScaleX;
                double positionDeltaY = (currentY - _TableMoveByDraggingMouseBeginY) / ctlDiagramView.stCurrentZoom.ScaleY;
                //double controlWidth   = _TableMoveByDraggingTableObject.ActualWidth /* ctlDiagramView.stCurrentZoom.ScaleX*/;
                //double controlHeight  = _TableMoveByDraggingTableObject.ActualHeight /* ctlDiagramView.stCurrentZoom.ScaleY*/;

                _TableMoveByDraggingMouseBeginX = currentX;
                _TableMoveByDraggingMouseBeginY = currentY;


                double currentTopValue = (double)(_TableMoveByDraggingTableObject.GetValue(Canvas.TopProperty));
                double currentLeftValue = (double)(_TableMoveByDraggingTableObject.GetValue(Canvas.LeftProperty));

                double finalTopValue = currentTopValue + positionDeltaY;
                double finalLeftValue = currentLeftValue + positionDeltaX;
                bool topOutOfBounds = false;
                bool leftOutOfBounds = false;

                IsTableDraggingOutOfBounds(
                                                _TableMoveByDraggingTableObject,
                                                finalLeftValue,
                                                finalTopValue,
                                                out topOutOfBounds,
                                                out leftOutOfBounds);

                if (topOutOfBounds)
                {
                    currentTopValue = finalTopValue;
                    _TableMoveByDraggingTableObject.SetValue(Canvas.TopProperty, currentTopValue);
                }

                if (leftOutOfBounds)
                {
                    currentLeftValue = finalLeftValue;
                    _TableMoveByDraggingTableObject.SetValue(Canvas.LeftProperty, currentLeftValue);
                }

                _TableMoveByDraggingTableObject.EntityTable_info.CanvasCoordinates = new Point(currentLeftValue, currentTopValue);

                // Update the table position on the XML Model
                MsgUpdateTableCanvasPosition msgUpdateTablePosition = new MsgUpdateTableCanvasPosition();

                msgUpdateTablePosition.MessageType_info = MessageType.TABLEOP_UpdateTableCanvasPosition;
                msgUpdateTablePosition.tableID = _TableMoveByDraggingTableObject.EntityTable_info.InternalID;
                msgUpdateTablePosition.newPosition = _TableMoveByDraggingTableObject.EntityTable_info.CanvasCoordinates;

                ((App)(Application.Current)).Controller.RunCommand(this, msgUpdateTablePosition);
            }
            else
            {
                // if no tracking and the mouse is over a blue header table just change the mouse cursor...

                Point mousePoint = new Point(currentX, currentY);


                // 1. Did we hit a table ??
                CtlDBTable tableHit = GetFirstTableHitFromPointPosition(mousePoint);

                //System.Collections.Generic.List<System.Windows.UIElement> hits = (System.Collections.Generic.List<System.Windows.UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(mousePoint, this);                
                //CtlDBTable tableHit = _dynamicCOntrolsListHelper.TableHeaderHit(hits);
                if (tableHit != null)
                {
                    // 2. Did we hit just the header of the table?
                    CtlDBTable tableHeaderHit = GetFirstTableHeaderHitFromPointPosition(mousePoint);

                    if (tableHeaderHit != null)
                        this.Cursor = Cursors.Hand;
                    //this.Cursor = (tableHeaderHit != null) ? Cursors.Hand : Cursors.Stylus;
                }
                else
                {
                    this.Cursor = Cursors.Arrow;
                }
            }


            // *************
            // Temporary relation Drag
            // *************
            // [RELDRAG] D2. Remove the temporary relation control once the Drag & Drop is finished
            // User can drop just in a table or outside
            //_temporaryRelationControlToShowMotionWhenDragging
            if (_temporaryRelationControlToShowMotionWhenDragging != null)
            {
                CtlDBRelationship currRelationship = _temporaryRelationControlToShowMotionWhenDragging;

                Point destination = e.GetPosition(this);

                destination = SumToolBarAddScrollAndZoomOffsetsToCurrentMousePosition(destination);


                _relationshipDraggingHelper.SetPointOriginAndDestinationFromTemporaryDragRelationship(_dynamicCOntrolsListHelper, currRelationship, destination);
            }

        }


        void DiagramView_OnMouseLBUp(object sender, MouseEventArgs e)
        {
            if (_TableMoveByDraggingTrackingMouse)
            {
                _TableMoveByDraggingTableObject.ReleaseMouseCapture();
                _TableMoveByDraggingTrackingMouse = false;
            }
         
            this.Cursor = Cursors.Arrow;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="finalLeftValue"></param>
        /// <param name="finalTopValue"></param>
        /// <param name="topOutOfBounds"></param>
        /// <param name="leftOutOfBounds"></param>
        /// <returns></returns>
        private void IsTableDraggingOutOfBounds(
                                                CtlDBTable tableControl,
                                                double finalLeftValue,
                                                double finalTopValue,
                                                out bool topOutOfBounds,
                                                out bool leftOutOfBounds)
        {

            double CanvasWidth = ctlDiagramView.LayoutRoot.Width;
            double CanvasHeight = ctlDiagramView.LayoutRoot.Height;
            double controlWidth = tableControl.ActualWidth;
            double controlHeight = tableControl.ActualHeight;


            leftOutOfBounds = (finalLeftValue >= 0 && (finalLeftValue + controlWidth) < CanvasWidth);
            topOutOfBounds = (finalTopValue >= 0 && (finalTopValue + controlHeight) < CanvasHeight);
        }



        /// <summary>
        /// Just to show something temporary, meanwhile an user is dragging a new relation
        /// </summary>
        /// <param name="newRelation"></param>
        /// <param name="Destination"></param>
        public CtlDBRelationship CreateTemporaryRelationControl(EntityRelation newRelation, Point destination)
        {
            CtlDBRelationship currRelationship = new CtlDBRelationship();

            ctlDiagramView.LayoutRoot.Children.Add(currRelationship);
            currRelationship.EntityRelation_info = newRelation;
            // Show it above the tables (just the tem drag relation)
            currRelationship.SetValue(Canvas.ZIndexProperty, 5);

            _relationshipDraggingHelper.SetPointOriginAndDestinationFromTemporaryDragRelationship(_dynamicCOntrolsListHelper, currRelationship, destination);


            return currRelationship;
        }


        /// <summary>
        /// Destination point on create relation need to add the scroll bar offsets in order to setup the right coordinate
        /// </summary>
        /// <param name="mousePosition"></param>
        /// <returns></returns>
        Point AddMousePositionDestinationCurrentScrollViewOffsets(Point mousePosition)
        {
            Point ret = mousePosition;


            ret.X += ctlDiagramView.ScrViewer.HorizontalOffset;
            ret.Y += ctlDiagramView.ScrViewer.VerticalOffset;

            return ret;
        }

        /// <summary>
        /// There must be a cleaner way, right now just toolbara YOffset
        /// </summary>
        /// <returns></returns>
        double GetDiagramCanvasYTopPosition()
        {
            // In our case we don't have a toolbar
            // Check what happens if you position the XAP not in 0,0 coordinates
            return (double)0; // inMainBar.ActualHeight;//ctlDiagramView.GetValue(Canvas.TopProperty);
        }


        /// <summary>
        /// Given a coordinate check if there is any Table Control just on that positionb
        /// </summary>
        /// <param name="position"></param>
        /// <returns>First table control found, if not found returns a NULL value</returns>
        public CtlDBTable GetFirstTableHeaderHitFromPointPosition(Point position)
        {
            System.Collections.Generic.List<System.Windows.UIElement> hits = (System.Collections.Generic.List<System.Windows.UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(position, this);

            CtlDBTable tableHit = _dynamicCOntrolsListHelper.TableHeaderHit(hits);

            return tableHit;
        }



        /// <summary>
        /// Given a coordinate check if there is any Table Control just on that positionb
        /// </summary>
        /// <param name="position"></param>
        /// <returns>First table control found, if not found returns a NULL value</returns>
        public CtlDBTable GetFirstTableHitFromPointPosition(Point position)
        {
            System.Collections.Generic.List<System.Windows.UIElement> hits = (System.Collections.Generic.List<System.Windows.UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(position, this);

            CtlDBTable tableHit = _dynamicCOntrolsListHelper.FullTableAreaHit(hits);

            return tableHit;
        }



        /// <summary>
        /// We will use this to map destination on temporary relations (need to add the current scroll, toolbar offset and scale it to
        /// the current zooming factor)
        /// </summary>
        /// <param name="mouseposition"></param>
        /// <returns></returns>
        Point SumToolBarAddScrollAndZoomOffsetsToCurrentMousePosition(Point mouseposition)
        {
            // Add the scroll offset
            mouseposition = AddMousePositionDestinationCurrentScrollViewOffsets(mouseposition);

            // Remolve the Y Toolbar size
            mouseposition.Y -= GetDiagramCanvasYTopPosition();

            // Add the scale factor to destination
            mouseposition = ScaleMousePositionToCurrentZoomingScale(mouseposition);

            return mouseposition;

        }



        /// <summary>
        /// When we wantr to use an absolute mouse position (e.g. temporary relation drag, destination) we need to take int consideration
        /// the current zoom factor.
        /// </summary>
        /// <param name="mousePosition"></param>
        /// <returns></returns>
        Point ScaleMousePositionToCurrentZoomingScale(Point mousePosition)
        {
            // Add the scale factor to destination
            mousePosition.X = (mousePosition.X / ctlDiagramView.stCurrentZoom.ScaleX);
            mousePosition.Y = (mousePosition.Y / ctlDiagramView.stCurrentZoom.ScaleY);

            return mousePosition;
        }

        /// <summary>
        /// This methods is called from javascritp when the user click on the Export HTML button
        /// </summary>
        [ScriptableMember]
        public void ExecuteExportImage()
        {
            //Show a Modal PopUp Window where the user can select
            //the type which the canvas has to be exported
            ExportWindow popUp = new ExportWindow(ctlDiagramView.LayoutRoot);

            //This events are thrown by the ExportWindow PopUp if the user
            //decides to unmark a selected object
            popUp.MarkSelectedObject += new EventHandler(popUp_MarkSelectedObject);
            popUp.UnMarkSelectedObject += new EventHandler(popUp_UnMarkSelectedObject);

            popUp.Show();
        }

        ISelectable _previousSelectedObject;

        /// <summary>
        /// Store the selected object and unmarks it
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void popUp_UnMarkSelectedObject(object sender, EventArgs e)
        {
            if (_currSelectedControl != null)
            {
                _currSelectedControl.unSelect();
            }

            _previousSelectedObject = _currSelectedControl;
        }

        /// <summary>
        /// Mark the previous selected object if exists
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void popUp_MarkSelectedObject(object sender, EventArgs e)
        {
            if (_previousSelectedObject != null)
            {
                _previousSelectedObject.Select();
            }

            _currSelectedControl = _previousSelectedObject;
        }   
    }
}
